using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.DataAccess.Common;
using T.FAS.Runtime.DataAccess.Kernel;
using T.FAS.Runtime.DataAccess.SPI;
using T.FAS.Runtime.Metadata.Entity.DataModel;
using T.ServiceComponent.Kernel.Service;

namespace T.FAS.Runtime.DataAccess.Engine
{
    public class CDOChangeSetService
    {
        private IQueryService<CompositeDataObject> queryService = new CDODataQueryService();

        /// <summary>
        /// 获取变更日志
        /// </summary>
        /// <param name="compositeDataObject"></param>
        /// <param name="changeSetType"></param>
        /// <param name="newRecord"></param>
        /// <param name="pk"></param>
        /// <param name="exrParams"></param>
        /// <returns></returns>
        public TRecord GetChangeSet(CompositeDataObject compositeDataObject, ChangeSetType changeSetType, TRecord newRecord = null, string pk = null, Dictionary<string, object> exrParams = null)
        {
            if (compositeDataObject == null)
                throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetChangeSet));
            var errorPrefix = $"获取CDO:{compositeDataObject?.Code}ChangeSet失败";
            var type = GetInjectionMethodType(changeSetType);

            //初始化CDO的CNO间引用
            compositeDataObject.InitCDO();

            ChangeLogContext changeLogContext = new ChangeLogContext
            {
                CompositeDataObject = compositeDataObject,
                InjectionMethodType = type,
                ExtParam = exrParams,
            };

            //构造鉴权上下文，notice：是否重复鉴权
            if (DACControlContextHelper.IsNeedAuthAndTryGetAuthResult(compositeDataObject, out List<ColumnPermissionView> columnPermissionResult))
            {
                changeLogContext.InitAuthControl(true, columnPermissionResult);
            }

            switch (type)
            {
                case InjectionMethodType.Create:
                    if (newRecord == null)
                        throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetChangeSet1,errorPrefix));
                    changeLogContext.CurrentData = newRecord;
                    break;
                case InjectionMethodType.Update:
                    if (newRecord == null)
                        throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetChangeSet2,errorPrefix));
                    changeLogContext.CurrentData = newRecord;
                    changeLogContext.PrevData = GetOldTRecord(compositeDataObject, newRecord);
                    break;
                case InjectionMethodType.Delete:
                    if (string.IsNullOrEmpty(pk))
                        throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetChangeSet3,errorPrefix));
                    break;
                case InjectionMethodType.Retrieve:
                default:
                    break;
            }

            TRecord result = null;
            var changeLog = ChangeLogFactory.GetInstance(type);
            if (type == InjectionMethodType.Create || type == InjectionMethodType.Update)
            {
                result = changeLog.LogChangeSet(changeLogContext);
            }
            else if (type == InjectionMethodType.Delete)
            {
                var oldData = GetOldTRecord(compositeDataObject, null, pk);
                oldData.ChangeType = TRecordChangeType.Delete;
                oldData.ChangeSet = new TChangeSet
                {
                    BillID = pk,
                    CDOID = compositeDataObject.ID,
                    CDOCode = compositeDataObject.Code,
                    ChangeType = TRecordChangeType.Delete,
                    PKColumnName = compositeDataObject?.RootCompositeNodeObject?.GetPKColumn()?.ColumnName,
                    PKColumnValue = pk,
                    ModifyTime = DateTime.Now,
                    Modifier = UserUtil.GetCurrentUserID(),
                    ModifierName = UserUtil.GetCurrentUserName(),
                    TenantID = UserUtil.GetCurrentUserTenantId(),
                };
                result = oldData;
            }
            FillInfo(compositeDataObject, result);
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="compositeDataObject"></param>
        /// <param name="newRecord"></param>
        /// <param name="ColKey"></param>
        /// <param name="EventKey"></param>
        /// <returns></returns>
        public TRecord GetFireEventChangeSet(CompositeDataObject compositeDataObject, TRecord newRecord, string ColKey, string EventKey)
        {
            var errorPrefix = $"获取CDO:{compositeDataObject?.Code}FireEvent ChangeSet失败";
            var pkName = compositeDataObject?.RootCompositeNodeObject?.GetPKColumn().ColumnName;
            var stateField = compositeDataObject?.RootCompositeNodeObject?.GetSpecifiedColumn(ColKey);
            if (string.IsNullOrEmpty(pkName))
                throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetFireEventChangeSet,errorPrefix));
            if(newRecord == null)
                throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetFireEventChangeSet1,errorPrefix));
            var pkVal = newRecord.GetValue(pkName)?.ToString();
            if (string.IsNullOrEmpty(pkVal))
                throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetFireEventChangeSet2,errorPrefix));
            var SMMetadata = RTPMetadataHelper.GetSMMetaData(compositeDataObject, ColKey).FirstOrDefault();
            if (SMMetadata == null)
                throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetFireEventChangeSet3,errorPrefix,ColKey));

            var smEvent = SMMetadata.Events.Where(e => e.Key == EventKey).FirstOrDefault();
            if (smEvent == null)
                throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetFireEventChangeSet4,errorPrefix,EventKey));
            var tran = SMMetadata.Transitions.Where(t => t.EventID == smEvent?.ID).FirstOrDefault();
            if (tran == null)
                throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetFireEventChangeSet5,errorPrefix,EventKey));
            var oldState = tran?.From;
            var newState = tran?.To;
            if (oldState == null)
                oldState = SMMetadata.States.FirstOrDefault(s => s.ID == tran?.FromStateID);
            if (newState == null)
                newState = SMMetadata.States.FirstOrDefault(s => s.ID == tran?.ToStateID);
            if (oldState == null || newState == null)
                throw new FasException(DataAccessErrorCode.ChangeSet.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetFireEventChangeSet6,errorPrefix,SMMetadata?.Code,EventKey));

            newRecord.ChangeType = TRecordChangeType.Update;
            newRecord.ChangeSet = new TChangeSet
            {
                BillID = pkVal,
                CDOID = compositeDataObject.ID,
                CDOCode = compositeDataObject.Code,
                ChangeType = TRecordChangeType.Update,
                PKColumnName = pkName,
                PKColumnValue = pkVal,
                ModifyTime = DateTime.Now,
                Modifier = UserUtil.GetCurrentUserID(),
                ModifierName = UserUtil.GetCurrentUserName(),
                TenantID = UserUtil.GetCurrentUserTenantId(),
            };
            newRecord.ChangeSet.ChangeSetItems.Add(new TChangeSetItem
            {
                BusinessType = BusinessType.State,
                ColumnCode = ColKey,
                ColumnName = stateField?.DisplayName,
                InitValue = oldState.Key,
                InitDisplayName = oldState.Name,
                OldValue = oldState.Key,
                OldDisplayValue = oldState.Name,
                NewValue = newState.Key,
                NewDisplayValue = newState.Name,
                DataType = DataType.Int,
            });

            FillInfo(compositeDataObject, newRecord);
            return newRecord;
        }

        #region Private Funcs

        /// <summary>
        /// 变更日志获取old record值
        /// </summary>
        /// <param name="compositeDataObject"></param>
        /// <param name="newRecord"></param>
        /// <param name="pk"></param>
        /// <returns></returns>
        /// <exception cref="FasException"></exception>
        private TRecord GetOldTRecord(CompositeDataObject compositeDataObject, TRecord newRecord = null, string pk = null)
        {
            //old Record目前是从数据库中取出
            TRecord oldTRecord = default;
            string pkVal = pk;
            try
            {
                if (string.IsNullOrEmpty(pkVal))
                    pkVal = newRecord.GetCDOTRecordPrimaryKeyValue(compositeDataObject);
                if (string.IsNullOrEmpty(pkVal))
                    throw new FasException(DataAccessErrorCode.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetOldTRecord));
                oldTRecord = ((TCommonResult<TRecord>)queryService.QueryData(compositeDataObject, pkVal, DACControlContextHelper.BuildDefultExtParams())).Record;
                if (oldTRecord == null)
                    throw new FasException(DataAccessErrorCode.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetOldTRecordFail,compositeDataObject?.Code,pkVal));
                return oldTRecord;
            }
            catch (Exception e)
            {
                throw new FasException(DataAccessErrorCode.Error, string.Format(CommonStringClassEngine.CDOChangeSetService_GetOldTRecordFail, compositeDataObject?.Code,pkVal), e);
            }
        }

        /// <summary>
        /// 填充初始化信息
        /// </summary>
        /// <param name="compositeDataObject"></param>
        /// <param name="newRecord"></param>
        private void FillInfo(CompositeDataObject compositeDataObject, TRecord newRecord)
        {
            if (compositeDataObject == null || newRecord == null) return;
            if (newRecord.AllColumnInfos == null)
                newRecord.AllColumnInfos = new Dictionary<string, string>();
            var normalEle = compositeDataObject.RootCompositeNodeObject.Elements.Where(e => e.ElementType == ElementType.Normal);
            foreach (var ele in normalEle)
            {
                newRecord.AllColumnInfos[ele.Alias] = ele.DisplayName;
            }
        }

        private InjectionMethodType GetInjectionMethodType(ChangeSetType changeSetType)
        {
            return (InjectionMethodType)((int)changeSetType);
        }

        #endregion
    }

    public enum ChangeSetType
    {
        Create = 0,
        Update = 1,
        Delete = 2,
    }
}
